import hashlib
import random
import uuid
import boto3
import requests
from configuration import get_boto3_client, get_boto3_resource, is_running_on_aws_lambda
import base64
import json
import time
from boto3.dynamodb.conditions import Key
import logging
from boto3.dynamodb.conditions import Attr
from decimal import Decimal

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
if not is_running_on_aws_lambda():
    handler = logging.StreamHandler()
    # format the log
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

class DecimalEncoder(json.JSONEncoder):
    """自定义JSON编码器，用于处理DynamoDB返回的Decimal类型"""
    def default(self, obj):
        if isinstance(obj, Decimal):
            return int(obj) if obj % 1 == 0 else float(obj)
        return super(DecimalEncoder, self).default(obj)

class UserInfo:
    
    def __init__(self, user_info: dict, platform: str, access_token: str):
        self.user_info = user_info
        self.Platform = platform
        self.platform = platform  # 添加小写platform属性以匹配@property方法
        self.access_token = access_token
        self.Token = access_token
    
    def to_dict(self):
        self.user_info['user_id'] = self.Id
        self.user_info['access_token'] = self.Token
        self.user_info['platform'] = self.Platform
        self.user_info['platform_user_id'] = self.PlatformId
        self.user_info['email'] = self.Email
        return self.user_info
    
    @property
    def Id(self):
        if self.platform == 'microsoft':
            return self.user_info.get('user_id', '')
        if self.platform == 'zhuchundashuaige':
            return self.user_info.get('user_id', '')

    @Id.setter
    def Id(self, value):
        if self.platform == 'microsoft':
            self.user_info['user_id'] = value
        if self.platform == 'zhuchundashuaige':
            self.user_info['user_id'] = value

    @property
    def PlatformId(self):
        if self.platform == 'microsoft':
            return self.Id
        if self.platform == 'zhuchundashuaige':
            return self.user_info.get('user_id', '')

    @PlatformId.setter
    def PlatformId(self, value):
        if self.platform == 'microsoft':
            self.Id = value
        if self.platform == 'zhuchundashuaige':
            self.user_info['user_id'] = value

    @property
    def DisplayName(self):
        if self.platform == 'microsoft':
            return self.user_info.get('displayName', '')
        if self.platform == 'zhuchundashuaige':
            return self.user_info.get('display_name', '')

    @DisplayName.setter
    def DisplayName(self, value):
        if self.platform == 'microsoft':
            self.user_info['displayName'] = value
        if self.platform == 'zhuchundashuaige':
            self.user_info['display_name'] = value

    @property
    def Email(self):
        if self.platform == 'microsoft':
            return self.user_info.get('mail', '')
        if self.platform == 'zhuchundashuaige':
            return self.user_info.get('email', '')
        

class UserInfoDynamoDB:
    
    def __init__(self, user_info: UserInfo = None):
        self.user_info: UserInfo = user_info
        
        dynamodb = get_boto3_resource('dynamodb')
        self.table = dynamodb.Table('zhuchundashuaige_user')
    
    def get(self, user_id: str = None, username: str = None):
        # 优先使用username和password查询，如果为空则使用user_id
        if username:
            return self._get_by_credentials(username)
        elif user_id:
            return self._get_by_user_id(user_id)
        else:
            logger.info("user_id和username/password都为空")
            return False, "用户不存在"
    
    def _get_by_credentials(self, username: str):
        """使用username和password查询用户信息"""
        try:
            # 使用scan查询，因为username和password不是主键
            response = self.table.scan(
                FilterExpression='username = :username',
                ExpressionAttributeValues={
                    ':username': username
                }
            )
            
            items = response.get('Items', [])
            if items:
                item = items[0]  # 取第一个匹配的用户
                logger.info(f"成功获取用户信息，username: {username}")
                # 从item中获取platform和token，如果self.user_info为None则使用默认值
                platform = item.get('platform', '')
                token = item.get('access_token', '')
                return True, UserInfo(item, platform, token)
            else:
                logger.warning(f"未找到用户信息，username: {username}")
                return False, "用户不存在"
        except Exception as e:
            logger.error(f"获取用户信息失败，username: {username}，错误: {str(e)}")
            return False, f"获取用户信息失败: {str(e)}"
    
    def _get_by_user_id(self, user_id: str):
        """使用user_id查询用户信息（保持向后兼容）"""
        if not user_id: 
            logger.info("user id is none")
            return False, "用户不存在"
        try:
            response = self.table.get_item(Key={'user_id': user_id})
            item = response.get('Item')
            if item:
                logger.info(f"成功获取用户信息，user_id: {user_id}")
                # 从item中获取platform和token，如果self.user_info为None则使用默认值
                platform = self.user_info.Platform if self.user_info else item.get('platform', '')
                token = self.user_info.Token if self.user_info else item.get('access_token', '')
                return True, UserInfo(item, platform, token)
            else:
                logger.warning(f"未找到用户信息，user_id: {user_id}")
                return False, "用户不存在"
        except Exception as e:
            logger.error(f"获取用户信息失败，user_id: {user_id}，错误: {str(e)}")
            return False, f"获取用户信息失败: {str(e)}"
        
    def _is_valid_dynamodb_key(self, key: str) -> bool:
        """检查键名是否适合用于 DynamoDB"""
        if not key or not isinstance(key, str):
            return False
        
        # 检查是否包含特殊字符
        problematic_chars = ['@', '#', '$', '%', '^', '&', '*', '(', ')', '+', '=', '[', ']', '{', '}', '|', '\\', ':', ';', '"', "'", '<', '>', ',', '?', '/']
        return not any(char in key for char in problematic_chars)
    
    def _clean_data_for_dynamodb(self, data: dict) -> dict:
        """清理数据字典，移除包含特殊字符的键名"""
        cleaned_data = {}
        for k, v in data.items():
            if self._is_valid_dynamodb_key(k):
                cleaned_data[k] = v
            else:
                logger.warning(f'跳过包含特殊字符的键名: {k}')
        return cleaned_data
    
    def save_change(self):
        try:
            self.user_info.user_info['created_at'] = int(time.time())
            if self.user_info.Id:
                response = self.table.get_item(Key={'user_id': self.user_info.Id})
                item = response.get('Item')
                if item:
                    self.user_info.user_info['created_at'] = item.get('created_at', int(time.time()))
            data = self.user_info.user_info
            data['id'] = self.user_info.Id
            data['platform'] = self.user_info.Platform
            data['token'] = self.user_info.Token
            data['platform_id'] = self.user_info.PlatformId
            data['display_name'] = self.user_info.DisplayName
            data['email'] = self.user_info.Email
            data['updated_at'] = int(time.time())
            if self.user_info.Id:
                # 过滤掉包含特殊字符的键名，避免 DynamoDB 表达式错误
                update_keys = [k for k in data.keys() if k != 'user_id' and self._is_valid_dynamodb_key(k)]
                
                if update_keys:
                    update_expr = 'SET ' + ', '.join([f"#{k} = :{k}" for k in update_keys])
                    expr_attr_names = {f"#{k}": k for k in update_keys}
                    expr_attr_values = {f":{k}": data[k] for k in update_keys}
                else:
                    logger.warning('没有有效的键名可以更新')
                    return True, data
                self.table.update_item(
                    Key={'user_id': self.user_info.Id},
                    UpdateExpression=update_expr,
                    ExpressionAttributeNames=expr_attr_names,
                    ExpressionAttributeValues=expr_attr_values
                )    
            else:
                # 过滤掉包含特殊字符的键名，避免 DynamoDB 错误
                cleaned_data = self._clean_data_for_dynamodb(data)
                logger.info(f"cleaned_data: {cleaned_data}")
                self.table.put_item(
                    Item=cleaned_data
                )
            return True, data
        except Exception as e:
            return False, f"保存用户信息失败: {str(e)}"


class ValidateService:
    Guid = "23aea1c6-792d-4468-9364-3141eb3c379a"
    AdminGuid = "01809a90-879a-4f4d-b927-670c35b93179"
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.user_id = None
        self.access_token = None
        self.platform = None
        self.platform_user_id = None
        
        try:
            user_id, access_token, platform, platform_user_id = base64.b64decode(self.api_key).decode('utf-8').split('$$')
            self.user_id = user_id
            self.access_token = access_token
            self.platform = platform
            self.platform_user_id = platform_user_id
        except:
            logger.error(f"Unauthorized, apikey is invalid: {self.api_key}")
            # 保持默认的None值
        
    def validate(self):
        if not self.access_token or not self.platform:
            return False, "Unauthorized, apikey is invalid"     
        
        validate_mappings = {
            'microsoft': self.validate_microsoft,
            'zhuchundashuaige': self.validate_zhuchundashuaige,
        }
        return validate_mappings[self.platform]()
    
    def validate_zhuchundashuaige(self):
        # Token是base64加密的usernameValidate$Service.Guid$password$Service.Guid$AppId
        if not self.access_token:
            return False, "访问令牌为空"
        
        username = None
        password = None
        app_id = None
        try:
            decoded_token = base64.b64decode(self.access_token).decode('utf-8')
            parts = decoded_token.split(f'${self.Guid}$')
            if len(parts) != 3:
                return False, "访问令牌格式不正确"
            username, password, app_id = parts
            logger.info(f"username: {username}, password: {password}, app_id: {app_id}")
        except Exception as e:
            logger.error(f"解析访问令牌失败: {str(e)}")
            return False, "访问令牌解析失败"
        
        is_success, exist_user_info = UserInfoDynamoDB().get(username=username)
        if is_success:
            # 登录操作
            if exist_user_info.user_info['password'] != hashlib.sha256(password.encode('utf-8')).hexdigest():
                return False, "用户名或密码错误"
            new_app_id = str(uuid.uuid4())
            hash_token = hashlib.sha256(f"{username}${self.Guid}${password}${self.Guid}${new_app_id}".encode('utf-8')).hexdigest()
            exist_user_info.user_info['access_token'] = hash_token
            exist_user_info.user_info['app_id'] = new_app_id
            exist_user_info.user_info['updated_at'] = int(time.time())
            UserInfoDynamoDB(exist_user_info).save_change()
            return True, exist_user_info
        
        if app_id != self.AdminGuid:
            return False, "缺少管理员密钥码"
        
        
        if is_running_on_aws_lambda():
            return False, "只有开发者才能进行注册账号的操作"

        user_id = str(uuid.uuid4())
        app_id = str(uuid.uuid4())
        hash_token = hashlib.sha256(f"{username}${self.Guid}${password}${self.Guid}${app_id}".encode('utf-8')).hexdigest()
        user_info = {
            'user_id': user_id,
            'platform': self.platform,
            'platform_user_id': user_id,
            'access_token': hash_token,
            'display_name': username,
            'email': username + '@zhuchundashuaige.com',
            'username': username,
            'password': hashlib.sha256(password.encode('utf-8')).hexdigest(),
            'app_id': app_id,
            'created_at': int(time.time()),
            'updated_at': int(time.time())
        }
        
        return True, UserInfo(user_info, self.platform, self.access_token)
        
    def validate_microsoft(self):
        try:
            response = requests.get(f"https://graph.microsoft.com/v1.0/me", headers={
                'Authorization': f'Bearer {self.access_token}'
            })
            if response.status_code != 200:
                try:
                    error_detail = response.text
                except Exception as e:
                    error_detail = f"无法获取错误详情: {str(e)}"
                logger.error(f"Validate microsoft failed: {response.status_code}, detail: {error_detail}")
                return False, f"验证Microsoft失败，状态码: {response.status_code}，详情: {error_detail}"
            user_info = response.json()
            return True, UserInfo(user_info, self.platform, self.access_token)
        except Exception as e:
            logger.error(f"Validate microsoft failed: {e}")
            return False, f"验证Microsoft异常: {str(e)}"

class PublicValidate(ValidateService):
    
    def __init__(self,  event: dict):
        headers = event.get('headers', {})
        self.api_key = Authorization.extract_api_key(headers)
        super().__init__(self.api_key)
    
    def validate(self):
        if not self.api_key:
            logger.error("Unauthorized, apikey is required")
            return False, "Unauthorized, apikey is required"
        
        user_info_dynamodb = UserInfoDynamoDB()
        
        # 优先使用user_id，如果为空则尝试从token中提取username和password
        if self.user_id:
            is_success, response = user_info_dynamodb.get(user_id=self.user_id)
        else:
            # 尝试从access_token中提取username和password
            if self.platform == 'zhuchundashuaige' and self.access_token:
                try:
                    decoded_token = base64.b64decode(self.access_token).decode('utf-8')
                    parts = decoded_token.split(f'${self.Guid}$')
                    if len(parts) == 3:
                        username, password, app_id = parts
                        is_success, response = user_info_dynamodb.get(username=username)
                    else:
                        return False, "访问令牌格式不正确"
                except Exception as e:
                    logger.error(f"解析访问令牌失败: {str(e)}")
                    return False, "访问令牌解析失败"
            else:
                return False, "用户ID为空且无法从令牌中提取用户信息"
        
        if not is_success:
            return False, response
        
        if not response.Token or not response.Platform:
            logger.error(f"Unauthorized, response: Token: {response.Token}, Platform: {response.Platform}")
            return False, "Unauthorized"
        
        if response.Platform != self.platform:
            logger.error(f"Unauthorized, platform is invalid: {self.platform}")
            return False, "Unauthorized"
        
        if response.PlatformId != self.platform_user_id:
            logger.error(f"Unauthorized, platform_user_id is invalid: {self.platform_user_id}")
            return False, "Unauthorized"
        
        validate_mappings = {
            'microsoft': self.validate_microsoft,
            'zhuchundashuaige': self.validate_zhuchundashuaige,
        }
        return validate_mappings[self.platform](response)
    
    def validate_zhuchundashuaige(self, user_info: UserInfo):
        last_login_time = user_info.user_info.get('updated_at', None)
        if last_login_time:
            if last_login_time < int(time.time()) - 60 * 60 * 2: # 2小时
                return False, "授权信息已过期"
        
        user_info.user_info['updated_at'] = int(time.time())
        user_info_dynamodb = UserInfoDynamoDB(user_info)
        is_success, response = user_info_dynamodb.save_change()
        if not is_success:
            logger.error(f"Save user info failed: {response}")

        return True, user_info
        
    def validate_microsoft(self, user_info: UserInfo):
        if user_info.Token != self.access_token:
            logger.error(f"Unauthorized, access_token is invalid: {self.access_token}")
            return False, "授权信息已过期"
        
        # 检查是否过期
        try:
            # 解析access_token，检查是否为JWT格式
            token_parts = self.access_token.split('.')
            if len(token_parts) != 3:
                logger.warning(f"无效的token格式: {self.access_token}")
                return False, "无效的授权信息"
            payload_b64 = token_parts[1] + '=' * (-len(token_parts[1]) % 4)
            try:
                payload_json = base64.urlsafe_b64decode(payload_b64).decode('utf-8')
                claims = json.loads(payload_json)
            except Exception as e:
                logger.warning(f"token payload解码失败: {e}")
                return False, "授权信息解析失败: " + str(e)
            current_time = int(time.time())
            if 'exp' not in claims or current_time > claims['exp']:
                logger.warning(f"Token已过期: {self.access_token}")
                return False, "授权信息已过期"
        except Exception as e:
            logger.error(f"检查token过期异常: {e}")
            return False, "授权信息解析失败: " + str(e)

        return True, user_info

    
class CloudServiceS3:
    s3_bucket = "zhuchundashuaige"
    
    def __init__(self, user_info: dict):
        self.user_info = user_info
        # 初始化时检查并创建用户目录
        self._ensure_user_directory_exists()
    
    def _ensure_user_directory_exists(self):
        """确保用户目录存在，如果不存在则创建"""
        try:
            s3_client = get_boto3_client('s3')
            # 检查用户根目录是否存在
            response = s3_client.list_objects_v2(
                Bucket=self.s3_bucket,
                Prefix=self.root_path,
                MaxKeys=1
            )
            
            # 如果目录不存在（没有对象），则创建一个空的目录标记
            if 'Contents' not in response or not response['Contents']:
                logger.info(f"用户目录不存在，正在创建: {self.root_path}")
                s3_client.put_object(
                    Bucket=self.s3_bucket,
                    Key=f"{self.root_path}/",
                    Body=''
                )
                logger.info(f"用户目录创建成功: {self.root_path}")
            else:
                logger.info(f"用户目录已存在: {self.root_path}")
                
        except Exception as e:
            logger.error(f"创建用户目录失败: {self.root_path}, 错误: {e}")
            # 不抛出异常，避免影响其他功能
    
    @property
    def root_path(self):
        return f"cloud/{self.user_info['user_id']}"
    
    def _get_s3_path(self, path: str, is_directory: bool = False) -> str:
        """统一的S3路径处理方法"""
        if path == '/':
            return self.root_path
        else:
            return f"{self.root_path}/{path.lstrip('/')}"
    
    def get_file_source_by_path(self, path: str):
        # 处理根路径的特殊情况
        if path == '/':
            s3_path = self.root_path
        else:
            s3_path = f"{self.root_path}/{path.lstrip('/')}"
        
        # 获取文件名
        filename = path.split('/')[-1]
        
        # 获取文件扩展名
        file_extension = ''
        if '.' in filename:
            file_extension = filename.split('.')[-1].lower()
        
        # 获取Content-Type
        content_type = self._get_content_type(file_extension)
        
        # 生成预签名URL
        download_url = self.generate_presigned_url(s3_path)
        
        # 构建响应
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'application/json',
                'Access-Control-Allow-Origin': '*',
                'Access-Control-Allow-Headers': 'Content-Type',
                'Access-Control-Allow-Methods': 'GET,POST,OPTIONS'
            },
            'body': json.dumps({
                'filename': filename,
                'download_url': download_url,
                'content_type': content_type,
                'expires_in': 7200  # 2小时有效期
            })
        }
    
    def is_dir(self, path: str):
        """判断路径是否为目录"""
        logger.info(f"检查是否为目录: {path}")
        
        # 根路径总是存在的
        if path == '/':
            logger.info("根路径，返回True")
            return True
            
        # 处理其他路径
        s3_client = get_boto3_client('s3')
        
        # 首先检查是否存在以 / 结尾的目录对象
        dir_s3_path = f"{self.root_path}/{path.lstrip('/')}/"
        logger.info(f"检查目录对象: {dir_s3_path}")
        
        try:
            response = s3_client.head_object(Bucket=self.s3_bucket, Key=dir_s3_path)
            content_length = response.get('ContentLength', 0)
            logger.info(f"目录对象存在，大小: {content_length}")
            if content_length == 0:
                logger.info("目录对象大小为0，确认是目录")
                return True
        except Exception as e:
            if "NoSuchKey" not in str(e):
                logger.error(f"检查目录对象失败: {e}")
                return False
        
        # 如果目录对象不存在，检查是否有子对象
        logger.info(f"目录对象不存在，检查子对象: {dir_s3_path}")
        try:
            response = s3_client.list_objects_v2(
                Bucket=self.s3_bucket,
                Prefix=dir_s3_path,
                MaxKeys=1
            )
            contents = response.get('Contents', [])
            logger.info(f"找到 {len(contents)} 个子对象")
            
            if len(contents) > 0:
                logger.info("有子对象，确认是目录")
                return True
            
            # 如果没有子对象，检查是否存在同名文件
            file_s3_path = f"{self.root_path}/{path.lstrip('/')}"
            logger.info(f"检查同名文件: {file_s3_path}")
            
            try:
                response = s3_client.head_object(Bucket=self.s3_bucket, Key=file_s3_path)
                content_length = response.get('ContentLength', 0)
                logger.info(f"同名文件存在，大小: {content_length}")
                if content_length > 0:
                    logger.info("同名文件存在且不为空，认为是文件")
                    return False
            except Exception as e:
                if "NoSuchKey" not in str(e):
                    logger.error(f"检查同名文件失败: {e}")
            
            logger.info("既不是目录也不是文件")
            return False
            
        except Exception as e:
            logger.error(f"检查子对象失败: {e}")
            return False
    
    def delete_path(self, path: str):
        """删除文件或目录（递归删除）"""
        # 处理根路径的特殊情况
        if path == '/':
            s3_path = self.root_path
        else:
            s3_path = f"{self.root_path}/{path.lstrip('/')}"
        s3_client = get_boto3_client('s3')
        
        try:
            # 列出所有需要删除的对象
            objects_to_delete = []
            paginator = s3_client.get_paginator('list_objects_v2')
            
            for page in paginator.paginate(Bucket=self.s3_bucket, Prefix=s3_path):
                if 'Contents' in page:
                    objects_to_delete.extend([{'Key': obj['Key']} for obj in page['Contents']])
            
            if not objects_to_delete:
                logger.warning(f"没有找到要删除的对象: {s3_path}")
                return
            
            # 批量删除对象（每次最多1000个）
            batch_size = 1000
            for i in range(0, len(objects_to_delete), batch_size):
                batch = objects_to_delete[i:i + batch_size]
                response = s3_client.delete_objects(
                    Bucket=self.s3_bucket,
                    Delete={'Objects': batch}
                )
                
                # 检查删除结果
                if 'Errors' in response and response['Errors']:
                    for error in response['Errors']:
                        logger.error(f"删除对象失败: {error}")
                
                logger.info(f"批量删除完成: {len(batch)} 个对象")
            
            logger.info(f"删除路径完成: {s3_path}, 共删除 {len(objects_to_delete)} 个对象")
            
        except Exception as e:
            logger.error(f"删除路径失败: {s3_path}, 错误: {e}")
            raise
    
    def rename_path(self, old_path: str, new_path: str):
        """重命名文件或目录"""
        # 处理根路径的特殊情况
        if old_path == '/':
            old_s3_path = self.root_path
        else:
            old_s3_path = f"{self.root_path}/{old_path.lstrip('/')}"
            
        if new_path == '/':
            new_s3_path = self.root_path
        else:
            new_s3_path = f"{self.root_path}/{new_path.lstrip('/')}"
        s3_client = get_boto3_client('s3')
        
        try:
            # 列出所有需要重命名的对象
            objects_to_rename = []
            paginator = s3_client.get_paginator('list_objects_v2')
            
            for page in paginator.paginate(Bucket=self.s3_bucket, Prefix=old_s3_path):
                if 'Contents' in page:
                    objects_to_rename.extend([obj['Key'] for obj in page['Contents']])
            
            if not objects_to_rename:
                logger.warning(f"没有找到要重命名的对象: {old_s3_path}")
                return
            
            # 逐个重命名对象
            for old_key in objects_to_rename:
                new_key = old_key.replace(old_s3_path, new_s3_path)
                
                # 复制对象到新位置
                s3_client.copy_object(
                    Bucket=self.s3_bucket,
                    CopySource=f"{self.s3_bucket}/{old_key}",
                    Key=new_key
                )
                
                # 删除原对象
                s3_client.delete_object(Bucket=self.s3_bucket, Key=old_key)
                
                logger.info(f"重命名对象: {old_key} -> {new_key}")
            
            logger.info(f"重命名路径完成: {old_s3_path} -> {new_s3_path}, 共重命名 {len(objects_to_rename)} 个对象")
            
        except Exception as e:
            logger.error(f"重命名路径失败: {old_s3_path} -> {new_s3_path}, 错误: {e}")
            raise
    
    def put_file(self, path: str, name: str, file_content: bytes):
        if not path:
            path = '/'
        # 处理根路径的特殊情况
        if path == '/':
            s3_path = f"{self.root_path}/{name}"
        else:
            s3_path = f"{self.root_path}/{path.lstrip('/')}/{name}"
        s3_client = get_boto3_client('s3')
        
        # 获取文件扩展名
        file_extension = ''
        if '.' in name:
            file_extension = name.split('.')[-1].lower()
        
        # 判断文件类型
        file_type = self._get_file_type(file_extension)
        
        # 根据文件类型设置Content-Type
        content_type = self._get_content_type(file_extension)
        
        # 上传文件
        s3_client.put_object(
            Bucket=self.s3_bucket, 
            Key=s3_path, 
            Body=file_content,
            ContentType=content_type
        )
        
        logger.info(f"文件已上传: {s3_path}, 类型: {file_type}, Content-Type: {content_type}, 大小: {len(file_content)} 字节")
    
    def put_text_file(self, path: str, name: str, text_content: str, encoding: str = 'utf-8'):
        """上传文本文件，自动转换为二进制"""
        file_content = text_content.encode(encoding)
        self.put_file(path, name, file_content)
    
    def _get_content_type(self, extension: str) -> str:
        """根据文件扩展名获取MIME类型"""
        content_types = {
            # HTML文件
            'html': 'text/html',
            'htm': 'text/html',
            
            # 音乐文件
            'mp3': 'audio/mpeg',
            'wav': 'audio/wav',
            'flac': 'audio/flac',
            'aac': 'audio/aac',
            'ogg': 'audio/ogg',
            'wma': 'audio/x-ms-wma',
            'm4a': 'audio/mp4',
            
            # 视频文件
            'mp4': 'video/mp4',
            'avi': 'video/x-msvideo',
            'mov': 'video/quicktime',
            'wmv': 'video/x-ms-wmv',
            'flv': 'video/x-flv',
            'webm': 'video/webm',
            'mkv': 'video/x-matroska',
            'm4v': 'video/x-m4v',
            
            # 图片文件
            'jpg': 'image/jpeg',
            'jpeg': 'image/jpeg',
            'png': 'image/png',
            'gif': 'image/gif',
            'bmp': 'image/bmp',
            'webp': 'image/webp',
            'svg': 'image/svg+xml',
            'ico': 'image/x-icon',
            
            # 文档文件
            'pdf': 'application/pdf',
            'doc': 'application/msword',
            'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'xls': 'application/vnd.ms-excel',
            'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'ppt': 'application/vnd.ms-powerpoint',
            'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'txt': 'text/plain',
            'rtf': 'application/rtf',
            
            # 压缩文件
            'zip': 'application/zip',
            'rar': 'application/vnd.rar',
            '7z': 'application/x-7z-compressed',
            'tar': 'application/x-tar',
            'gz': 'application/gzip',
            'bz2': 'application/x-bzip2',
            
            # 代码文件
            'py': 'text/x-python',
            'js': 'application/javascript',
            'html': 'text/html',
            'css': 'text/css',
            'java': 'text/x-java-source',
            'cpp': 'text/x-c++src',
            'c': 'text/x-csrc',
            'php': 'text/x-php',
            'rb': 'text/x-ruby',
            'go': 'text/x-go',
            'rs': 'text/x-rust',
            'json': 'application/json',
            'xml': 'application/xml',
            
            # 可执行文件
            'exe': 'application/x-msdownload',
            'msi': 'application/x-msi',
            'dmg': 'application/x-apple-diskimage',
            'deb': 'application/vnd.debian.binary-package',
            'rpm': 'application/x-rpm',
            
            # 其他常见文件
            'csv': 'text/csv',
            'md': 'text/markdown',
            'log': 'text/plain',
            'ini': 'text/plain',
            'conf': 'text/plain',
            'sh': 'application/x-sh',
            'bat': 'application/x-msdos-program',
            'ps1': 'application/x-powershell'
        }
        
        return content_types.get(extension, 'application/octet-stream')
    
    def create_dir(self, path: str, name: str):
        """创建目录"""
        logger.info(f"创建目录: path={path}, name={name}")
        
        # 构建S3路径
        if path == '/':
            s3_path = f"{self.root_path}/{name}/"
        else:
            s3_path = f"{self.root_path}/{path.lstrip('/')}/{name}/"
        
        logger.info(f"最终S3路径: {s3_path}")
        
        s3_client = get_boto3_client('s3')
        s3_client.put_object(Bucket=self.s3_bucket, Key=s3_path)
        
        logger.info(f"目录创建成功: {s3_path}")
    
    def _get_directory_size(self, dir_prefix: str) -> int:
        """获取目录对象本身的大小"""
        s3_client = get_boto3_client('s3')
        
        try:
            # 获取目录对象本身的信息
            response = s3_client.head_object(Bucket=self.s3_bucket, Key=dir_prefix)
            dir_size = response.get('ContentLength', 0)
            logger.info(f"目录 {dir_prefix} 大小: {dir_size} 字节")
            return dir_size
            
        except Exception as e:
            # 如果目录对象不存在，返回0
            logger.info(f"目录对象不存在或获取大小失败: {dir_prefix}, 错误: {e}")
            return 0
    
    def generate_presigned_url(self, s3_key: str, expiration: int = 7200) -> str:
        """生成预签名URL，默认2小时有效期"""
        try:
            s3_client = get_boto3_client('s3')
            url = s3_client.generate_presigned_url(
                'get_object',
                Params={'Bucket': self.s3_bucket, 'Key': s3_key},
                ExpiresIn=expiration
            )
            logger.info(f"生成预签名URL成功: {s3_key}, 有效期: {expiration}秒")
            return url
        except Exception as e:
            logger.error(f"生成预签名URL失败: {s3_key}, 错误: {e}")
            return ""
    
    def download_file_from_url(self, url: str, path: str = '/', custom_filename: str = None) -> dict:
        """从URL下载文件到指定目录"""
        # 参数验证
        if not url or not isinstance(url, str):
            raise Exception("URL参数无效")
        
        if not path or not isinstance(path, str):
            path = '/'
        
        logger.info(f"download_file_from_url 参数: url={url}, path={path}, custom_filename={custom_filename}")
        
        try:
            import requests
            import os
            from urllib.parse import urlparse
            
            logger.info(f"开始离线下载: URL={url}, 目标路径={path}")
            
            # 设置请求头
            headers = {
                'User-Agent': 'Mozilla/5.0 (compatible; CloudDownloader/1.0)',
                'Accept': '*/*',
                'Accept-Encoding': 'gzip, deflate',
                'Connection': 'keep-alive'
            }
            
            # 首先尝试HEAD请求获取文件信息
            try:
                logger.info(f"发送HEAD请求到: {url}")
                head_response = requests.head(url, headers=headers, timeout=10, allow_redirects=True)
                logger.info(f"HEAD请求状态码: {head_response.status_code}")
            except requests.exceptions.Timeout:
                logger.warning(f"HEAD请求超时: {url}")
                head_response = None
            except requests.exceptions.ConnectionError:
                logger.warning(f"HEAD请求连接错误: {url}")
                head_response = None
            except Exception as e:
                logger.warning(f"HEAD请求失败: {e}")
                head_response = None
            
            # 获取文件名
            if custom_filename:
                filename = custom_filename
                logger.info(f"使用自定义文件名: {filename}")
            else:
                # 从URL或Content-Disposition头获取文件名
                filename = None
                
                # 尝试从Content-Disposition头获取文件名
                if head_response:
                    content_disposition = head_response.headers.get('Content-Disposition', '')
                    if 'filename=' in content_disposition:
                        import re
                        match = re.search(r'filename=["\']?([^"\']+)["\']?', content_disposition)
                        if match:
                            filename = match.group(1)
                
                # 如果还是没有文件名，从URL路径获取
                if not filename:
                    parsed_url = urlparse(url)
                    filename = os.path.basename(parsed_url.path)
                
                # 如果还是没有文件名，使用默认名称
                if not filename or filename == '':
                    # 尝试从Content-Type推断文件扩展名
                    content_type = head_response.headers.get('Content-Type', '') if head_response else ''
                    if 'image' in content_type:
                        filename = 'downloaded_image.jpg'
                    elif 'video' in content_type:
                        filename = 'downloaded_video.mp4'
                    elif 'audio' in content_type:
                        filename = 'downloaded_audio.mp3'
                    elif 'text' in content_type:
                        filename = 'downloaded_text.txt'
                    else:
                        filename = 'downloaded_file'
                
                logger.info(f"自动生成文件名: {filename}")
            
            # 获取文件大小（从HEAD响应中）
            expected_size = 0
            if head_response:
                expected_size = int(head_response.headers.get('Content-Length', 0))
                logger.info(f"预期文件大小: {expected_size} 字节")
            
            # 下载文件内容
            logger.info(f"开始下载文件内容...")
            try:
                response = requests.get(url, headers=headers, timeout=30, stream=True, allow_redirects=True)
                logger.info(f"GET请求状态码: {response.status_code}")
            except requests.exceptions.Timeout:
                raise Exception(f"下载请求超时: {url}")
            except requests.exceptions.ConnectionError:
                raise Exception(f"下载连接错误: {url}")
            except Exception as e:
                raise Exception(f"下载请求失败: {e}")
            
            if response.status_code != 200:
                raise Exception(f"无法访问URL，状态码: {response.status_code}")
            
            # 使用流式下载，避免内存问题
            file_content = b''
            downloaded_size = 0
            
            logger.info(f"开始流式下载...")
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    file_content += chunk
                    downloaded_size += len(chunk)
                    
                    # 检查文件大小限制（200MB）
                    if downloaded_size > 200 * 1024 * 1024:
                        raise Exception(f"文件过大，当前大小: {downloaded_size} 字节，最大允许: 200MB")
                    
                    # 每下载1MB打印一次进度
                    if downloaded_size % (1024 * 1024) == 0:
                        logger.info(f"已下载: {downloaded_size // (1024 * 1024)}MB")
            
            logger.info(f"流式下载完成，总大小: {downloaded_size} 字节")
            
            # 验证下载是否完整
            if downloaded_size == 0:
                raise Exception("下载的文件内容为空")
            
            if expected_size > 0 and downloaded_size != expected_size:
                logger.warning(f"文件大小不匹配: 预期 {expected_size} 字节，实际 {downloaded_size} 字节")
            
            logger.info(f"文件下载完成: {downloaded_size} 字节")
            
            # 上传到S3
            self.put_file(path, filename, file_content)
            
            # 获取文件信息
            file_info = {
                'filename': filename,
                'size': downloaded_size,
                'size_mb': round(downloaded_size / 1024 / 1024, 2),
                'url': url,
                'path': f"{path}/{filename}",
                'content_type': response.headers.get('Content-Type', 'application/octet-stream')
            }
            
            logger.info(f"离线下载成功: {filename}, 大小: {file_info['size_mb']}MB")
            return file_info
            
        except Exception as e:
            logger.error(f"离线下载失败: {url}, 错误: {e}")
            raise Exception(f"下载失败: {str(e)}")
    
    def list_current_structure(self, path: str = '/'):
        """列出指定路径下的文件和目录结构，返回详细信息"""
        # 处理根路径的特殊情况
        if path == '/':
            s3_path = self.root_path + '/'
        else:
            s3_path = f"{self.root_path}/{path.lstrip('/')}/"
        
        logger.info(f"开始扫描目录: path={path}, s3_path={s3_path}")
        
        s3_client = get_boto3_client('s3')
        try:
            # 使用Delimiter来正确分离目录和文件
            logger.info(f"开始调用list_objects_v2 with Delimiter: Bucket={self.s3_bucket}, Prefix={s3_path}, Delimiter='/'")
            response = s3_client.list_objects_v2(
                Bucket=self.s3_bucket,
                Prefix=s3_path,
                Delimiter='/'
            )
            
            logger.info(f"S3响应: CommonPrefixes数量={len(response.get('CommonPrefixes', []))}, Contents数量={len(response.get('Contents', []))}")
            
            items = []
            total_size = 0
            
            # 处理CommonPrefixes（直接子目录）
            for common_prefix in response.get('CommonPrefixes', []):
                prefix_key = common_prefix['Prefix']
                dir_name = prefix_key.rstrip('/').split('/')[-1]
                
                # 递归获取子目录的内容
                sub_path = f"{path.rstrip('/')}/{dir_name}"
                sub_result = self.list_current_structure(sub_path)
                
                dir_info = {
                    'filename': dir_name,
                    'file_type': 'directory',
                    'is_directory': True,
                    'size': 0,
                    'md5': '',
                    'last_modified': '',
                    'created_time': '',
                    'full_path': prefix_key,
                    'items': sub_result.get('items', []),
                    'total_count': sub_result.get('total_count', 0),
                    'total_size': sub_result.get('total_size', 0)
                }
                items.append(dir_info)
                logger.info(f"添加目录 (CommonPrefix): {dir_name}, 子项目数量: {sub_result.get('total_count', 0)}")

            # 处理Contents（当前目录下的文件）
            for obj in response.get('Contents', []):
                key = obj['Key']
                
                # 跳过当前目录对象本身
                if key == s3_path:
                    logger.info(f"跳过当前目录对象: {key}")
                    continue
                
                filename = key.split('/')[-1]
                
                # 获取文件扩展名
                file_extension = ''
                if '.' in filename:
                    file_extension = filename.split('.')[-1].lower()
                
                file_type = self._get_file_type(file_extension)
                file_size = obj['Size']
                
                file_info = {
                    'filename': filename,
                    'file_type': file_type,
                    'is_directory': False,
                    'size': file_size,
                    'md5': obj.get('ETag', '').strip('"'),
                    'last_modified': obj['LastModified'].isoformat(),
                    'created_time': obj['LastModified'].isoformat(),
                    'full_path': key
                }
                items.append(file_info)
                logger.info(f"添加文件 (Content): {filename}, 类型: {file_type}, 大小: {file_size}")
                total_size += file_size

            result = {
                'path': path,
                'is_directory': True,
                'items': items,
                'total_count': len(items),
                'total_size': total_size
            }
            
            logger.info(f"扫描完成: 找到 {len(items)} 个项目, 总大小: {total_size}")
            
            return result
            
        except Exception as e:
            logger.error(f"获取文件结构失败: {e}", exc_info=True)
            return {
                'path': path,
                'is_directory': True,
                'items': [],
                'total_count': 0,
                'total_size': 0,
                'error': str(e)
            }
    
    def _get_current_level_items(self, s3_path: str, contents: list) -> list:
        """获取当前路径下的直接文件和目录（不递归）"""
        items = []
        processed_dirs = set()
        
        # 第一步：识别所有目录对象（以/结尾且size为0）
        for obj in contents:
            key = obj['Key']
            size = obj.get('Size', 0)
            
            # 跳过当前目录本身
            if key == s3_path or key == s3_path.rstrip('/'):
                continue
            
            # 获取相对于当前路径的文件名
            relative_path = key[len(s3_path):].lstrip('/')
            if not relative_path:
                continue
            
            # 如果是目录对象（以/结尾且size为0）
            if key.endswith('/') and size == 0:
                dir_name = relative_path.rstrip('/')
                if dir_name not in processed_dirs:
                    dir_info = {
                        'filename': dir_name,
                        'file_type': 'directory',
                        'is_directory': True,
                        'size': size,
                        'md5': obj.get('ETag', '').strip('"'),
                        'last_modified': obj['LastModified'].isoformat(),
                        'created_time': obj['LastModified'].isoformat(),
                        'full_path': key
                    }
                    items.append(dir_info)
                    processed_dirs.add(dir_name)
                    logger.info(f"添加目录: {dir_name}")
        
        # 第二步：识别所有文件
        for obj in contents:
            key = obj['Key']
            size = obj.get('Size', 0)
            
            # 跳过当前目录本身
            if key == s3_path or key == s3_path.rstrip('/'):
                continue
            
            # 获取相对于当前路径的文件名
            relative_path = key[len(s3_path):].lstrip('/')
            if not relative_path:
                continue
            
            # 如果是文件（不以/结尾或size不为0）
            if not key.endswith('/') or size > 0:
                # 检查是否是当前级别的文件
                if '/' not in relative_path:
                    filename = relative_path
                    
                    # 获取文件扩展名
                    file_extension = ''
                    if '.' in filename:
                        file_extension = filename.split('.')[-1].lower()
                    
                    # 判断文件类型
                    file_type = self._get_file_type(file_extension)
                    
                    file_info = {
                        'filename': filename,
                        'file_type': file_type,
                        'is_directory': False,
                        'size': size,
                        'md5': obj.get('ETag', '').strip('"'),
                        'last_modified': obj['LastModified'].isoformat(),
                        'created_time': obj['LastModified'].isoformat(),
                        'full_path': key
                    }
                    items.append(file_info)
                    logger.info(f"添加文件: {filename}, 类型: {file_type}, 大小: {size}")
                else:
                    # 这是子目录中的文件，获取目录名
                    dir_name = relative_path.split('/')[0]
                    if dir_name not in processed_dirs:
                        # 创建目录项
                        dir_info = {
                            'filename': dir_name,
                            'file_type': 'directory',
                            'is_directory': True,
                            'size': 0,
                            'md5': '',
                            'last_modified': '',
                            'created_time': '',
                            'full_path': f"{s3_path}{dir_name}/"
                        }
                        items.append(dir_info)
                        processed_dirs.add(dir_name)
                        logger.info(f"添加目录: {dir_name}")
        
        return items
    
    def _get_directory_items(self, dir_prefix: str) -> list:
        """获取指定目录下的所有文件（递归）"""
        s3_client = get_boto3_client('s3')
        items = []
        
        try:
            response = s3_client.list_objects_v2(
                Bucket=self.s3_bucket,
                Prefix=dir_prefix
            )
            
            for obj in response.get('Contents', []):
                key = obj['Key']
                
                # 获取相对于目录前缀的文件名
                relative_path = key[len(dir_prefix):].lstrip('/')
                if not relative_path:  # 跳过空文件名
                    continue
                
                # 获取文件扩展名
                file_extension = ''
                if '.' in relative_path:
                    file_extension = relative_path.split('.')[-1].lower()
                
                # 判断文件类型
                file_type = self._get_file_type(file_extension)
                
                file_size = obj['Size']
                
                file_info = {
                    'filename': relative_path,
                    'file_type': file_type,
                    'is_directory': False,
                    'size': file_size,
                    'md5': obj.get('ETag', '').strip('"'),
                    'last_modified': obj['LastModified'].isoformat(),
                    'created_time': obj['LastModified'].isoformat(),
                    'full_path': key
                }
                items.append(file_info)
                
        except Exception as e:
            logger.error(f"获取目录内容失败: {dir_prefix}, 错误: {e}")
        
        return items
    
    def _get_file_type(self, extension: str) -> str:
        """根据文件扩展名判断文件类型"""
        image_extensions = {'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'ico'}
        video_extensions = {'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv', 'm4v'}
        audio_extensions = {'mp3', 'wav', 'flac', 'aac', 'ogg', 'wma', 'm4a'}
        document_extensions = {'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'rtf'}
        archive_extensions = {'zip', 'rar', '7z', 'tar', 'gz', 'bz2'}
        code_extensions = {'py', 'js', 'html', 'css', 'java', 'cpp', 'c', 'php', 'rb', 'go', 'rs', 'json', 'xml'}
        executable_extensions = {'exe', 'msi', 'dmg', 'deb', 'rpm', 'bat', 'sh', 'ps1'}
        
        if extension in image_extensions:
            return 'image'
        elif extension in video_extensions:
            return 'video'
        elif extension in audio_extensions:
            return 'audio'
        elif extension in document_extensions:
            return 'document'
        elif extension in archive_extensions:
            return 'archive'
        elif extension in code_extensions:
            return 'code'
        elif extension in executable_extensions:
            return 'executable'
        elif extension == '':
            return 'unknown'
        else:
            return 'other'

class Request:
    USER_AGENTS = [
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
        'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
    ]

    def get_random_headers():
        """生成随机请求头"""
        return {
            'User-Agent': random.choice(Request.USER_AGENTS),
            'Accept': 'application/json, text/plain, */*',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'Accept-Encoding': 'gzip, deflate',
            'Connection': 'keep-alive',
            'Cache-Control': 'no-cache',
            'Pragma': 'no-cache',
            'Referer': 'http://f.y41566.com/',
            'Origin': 'http://f.y41566.com',
            'X-Requested-With': 'XMLHttpRequest'
        }
        
    @staticmethod
    def make_request_with_retry(url, method='GET', json_data=None, max_retries=3, base_delay=1):
        """带重试机制的请求函数"""
        session = requests.Session()
        
        for attempt in range(max_retries):
            try:
                # 随机延迟
                delay = base_delay + random.uniform(0.5, 2.0)
                time.sleep(delay)
                
                headers = Request.get_random_headers()
                
                if method.upper() == 'GET':
                    response = session.get(url, headers=headers, timeout=10)
                else:
                    response = session.post(url, headers=headers, json=json_data, timeout=10)
                
                # 检查响应状态
                if response.status_code == 200:
                    return response
                elif response.status_code == 429:  # Too Many Requests
                    logger.warning(f'Rate limited, attempt {attempt + 1}/{max_retries}')
                    time.sleep(delay * 2)  # 加倍延迟
                    continue
                else:
                    logger.error(f'Request failed with status {response.status_code}, attempt {attempt + 1}/{max_retries}')
                    
            except requests.exceptions.RequestException as e:
                logger.error(f'Request exception on attempt {attempt + 1}/{max_retries}: {e}')
                if attempt < max_retries - 1:
                    time.sleep(delay * 2)
                    continue
                
            # 最后一次尝试失败
            if attempt == max_retries - 1:
                return None
        
        return None
    
    
class Response:
    @staticmethod
    def build_error_response(error: str, status_code: int = 500):
        return Response.build_response({'error': error}, status_code)
    
    @staticmethod
    def build_success_response(body: dict, status_code: int = 200):
        return Response.build_response(body, status_code)
    
    @staticmethod
    def build_response(body: dict, status_code: int = 200):
        return {
            'statusCode': status_code,
            'headers': {
                'Content-Type': 'application/json'
            },
            'body': json.dumps(body, cls=DecimalEncoder)
        }
        
class MusicServiceS3:
    s3_bucket = "zhuchundashuaige"
    
    def __init__(self, user_info: dict):
        self.user_info = user_info
        
    def delete_music_by_id(self, id: str):
        s3_path = self.get_music_s3_path(id)
        s3_client = get_boto3_client('s3')
        s3_client.delete_object(Bucket=self.s3_bucket, Key=s3_path)
    
    def get_music_s3_path(self, id: str):
        return f"music_service/{self.user_info['user_id']}/{id}.json"
    
    def get_music_by_id(self, id: str):
        s3_path = self.get_music_s3_path(id)
        s3_client = get_boto3_client('s3')
        try:
            logger.info(f"从S3获取音乐信息: {s3_path}")
            response = s3_client.get_object(Bucket=self.s3_bucket, Key=s3_path)
            return json.loads(response.get('Body').read().decode('utf-8'))
        except Exception as e:
            if "NoSuchKey" in str(e):
                logger.info(f"S3中不存在音乐信息: {s3_path}")
            else:
                logger.error(f"从S3获取音乐信息失败: {e}")
            return None
    
    def set_music_by_id(self, id: str, music_info: dict):
        s3_path = self.get_music_s3_path(id)
        s3_client = get_boto3_client('s3')
        s3_client.put_object(Bucket=self.s3_bucket, Key=s3_path, Body=json.dumps(music_info, cls=DecimalEncoder))
        
    def get_music_by_url(self, url: str):
        ids = self.list_all_ids()
        for id in ids:
            music_info = self.get_music_by_id(id)
            if music_info and music_info.get('url') == url:
                return music_info
        return None
        
    def list_all_ids(self):
        result = []
        s3_client = get_boto3_client('s3')
        response = s3_client.list_objects_v2(Bucket=self.s3_bucket, Prefix=f"music_service/{self.user_info['user_id']}/")
        for obj in response.get('Contents', []):
            result.append(obj.get('Key').split('/')[-1].split('.')[0])
        return result
    
    def list_all_music(self):
        ids = self.list_all_ids()
        logger.info(f"获取所有音乐ID: {ids}")
        result = []
        for id in ids:
            music_info = self.get_music_by_id(id)
            if music_info:
                result.append(music_info)
        return result
 
class DynamoDBKayValue:
    SearchServiceKeyGuid = "6ebb9cd9-cf16-4aa8-8364-028425dd40fd"
    MusicServiceKeyGuid = "8bc62586-b1ce-4713-a852-bd07ce232a97"
    
    def __init__(self, query: dict, user_info: dict):
        self.query = query
        self.user_info = user_info
    
    @staticmethod
    def get_search_key(key: str):
        if not key:
            return None
        if len(key) > 8:
            key = key[:8]
        return  f'{DynamoDBKayValue.SearchServiceKeyGuid}_{key}'
    
    @property
    def search_key(self):
        search_keyword = self.query.get("search_keyword", "").strip()
        search_type = self.query.get("search_type")
        search_id = self.query.get("id", "")
        if not search_keyword or not search_type:
            return None
        if len(search_keyword) > 8:
            search_keyword = search_keyword[:8]
        search_key = f'{DynamoDBKayValue.SearchServiceKeyGuid}_{search_type}_{search_keyword}'
        if search_id:
            search_key = f'{DynamoDBKayValue.SearchServiceKeyGuid}_{search_type}_{search_keyword}_{search_id}'
        return search_key
    
    def get_search_value(self):
        if not self.search_key:
            return None
        logger.info(f'Generated search key: {self.search_key}')
        value_str = DynamoDBKayValue.get_value(self.search_key)
        if value_str is None:
            return None
        try:
            return json.loads(value_str)
        except Exception as e:
            logger.error(f'parse search value error: {e}, value_str: {value_str}')
            return None
    
    def set_search_value(self, value: dict):
        if not self.search_key:
            return
        if (not isinstance(value, (dict, list))) or not value:
            logger.warning(f'Invalid value or key for set_search_value: value={type(value)}, key={self.search_key}')
            return
        value_str = json.dumps(value)
        logger.info(f'Setting search value with key: {self.search_key}')
        DynamoDBKayValue.set_value(self.search_key, value_str, 60 * 60 * 24)
    
    @staticmethod
    def list_music_keys(id: str):
        match_key = f'{DynamoDBKayValue.SearchServiceKeyGuid}_1'
        match_key_2 = str(id)
        table = DynamoDBKayValue.get_db_table()
        response = table.scan(
            FilterExpression=Attr('key').contains(match_key) & Attr('key').contains(match_key_2)
        )
        items = response.get('Items', [])
        return items
    
    @staticmethod
    def update_music_value(id: str, value: dict):
        items = DynamoDBKayValue.list_music_keys(id)
        for item in items:
            key = item.get('key')
            if key:
                logger.info(f'更新音乐value: {key}')
                # 只修改url字段
                try:
                    old_value = json.loads(item.get('value', '{}'))
                except Exception as e:
                    logger.error(f'解析旧的音乐信息失败: {e}, value: {item.get("value")}')
                    old_value = {}
                if isinstance(old_value, dict):
                    old_value['url'] = value.get('url', old_value.get('url'))
                    value = old_value
                DynamoDBKayValue.set_value(key, json.dumps(value), 60 * 60 * 24)
    
    @staticmethod
    def get_music_value(id: str):
        match_key = f'{DynamoDBKayValue.SearchServiceKeyGuid}_1'
        match_key_2 = str(id)
        table = DynamoDBKayValue.get_db_table()
        try:
                # 扫描所有key，查找同时包含match_key和match_key_2的key
            filter_expression = None
            filter_expression = Attr('key').contains(match_key) & Attr('key').contains(match_key_2)
            response = table.scan(
                FilterExpression=filter_expression
            )
            items = response.get('Items', [])
            if not items:
                return None
            # 取create_time最大的项
            item = max(items, key=lambda x: x.get('create_time', 0))
            value_str = item.get('value', '')
            try:
                return json.loads(value_str)
            except Exception as e:
                logger.error(f'解析music value失败: {e}, value_str: {value_str}')
                return None
        except Exception as e:
            logger.error(f'获取music value失败: {e}')
            return None

    @staticmethod
    def clean_key_for_dynamodb(key: str) -> str:
        """
        清理key，移除或替换可能导致DynamoDB问题的特殊字符
        特别是处理像 @odata.context 这样的特殊字符key
        """
        if not key or not isinstance(key, str):
            return ""
        
        # 移除首尾空格
        clean_key = key.strip()
        
        # 过滤掉包含特殊字符的key
        # 检查是否包含 @ 符号（如 @odata.context）
        if '@' in clean_key:
            logger.warning(f'Filtering out key with @ symbol: {clean_key}')
            return ""
        
        # 检查是否包含其他可能导致问题的特殊字符
        problematic_chars = ['@', '#', '$', '%', '^', '&', '*', '(', ')', '+', '=', '[', ']', '{', '}', '|', '\\', ':', ';', '"', "'", '<', '>', ',', '?', '/']
        for char in problematic_chars:
            if char in clean_key:
                logger.warning(f'Filtering out key with problematic character "{char}": {clean_key}')
                return ""
        
        # 检查key是否为空或过短
        if len(clean_key) < 1:
            logger.warning(f'Key too short after cleaning: {clean_key}')
            return ""
        
        return clean_key

    @staticmethod
    def clean_dict_for_dynamodb(data: dict) -> dict:
        """
        清理字典中的所有key，移除包含特殊字符的key
        """
        if not isinstance(data, dict):
            return {}
        
        cleaned_data = {}
        for key, value in data.items():
            clean_key = DynamoDBKayValue.clean_key_for_dynamodb(key)
            if clean_key:  # 只有当key被清理后不为空时才保留
                cleaned_data[clean_key] = value
            else:
                logger.info(f'Removed key with special characters: {key}')
        
        return cleaned_data

    @staticmethod
    def get_db_table():
        table_name = 'zhuchundashuaige_kv' # 三个字段， key-字符串，value-字符串，create_time-datetime-时间戳，expire_sec-long-过期时间
        dynamodb = get_boto3_resource('dynamodb')
        table = dynamodb.Table(table_name)
        return table
    
    @staticmethod
    def get_value(key: str):
        if not key or not isinstance(key, str):
            logger.error(f'Invalid key: {key}')
            return None
        
        # 使用新的清理函数来处理特殊字符
        clean_key = DynamoDBKayValue.clean_key_for_dynamodb(key)
        if not clean_key:
            logger.error(f'Key filtered out due to special characters: {key}')
            return None
            
        logger.info(f'Getting value for key: {clean_key}')
        table = DynamoDBKayValue.get_db_table()
        
        try:
            response = table.get_item(Key={'key': clean_key})
            item = response.get('Item', {})
            if not item:
                logger.info(f'No item found for key: {clean_key}')
                return None
            expire_sec = item.get('expire_sec')
            create_time = item.get('create_time')
            if expire_sec is not None and create_time is not None:
                now = int(time.time())
                if now > int(create_time) + int(expire_sec):
                    logger.info(f'Item expired for key: {clean_key}')
                    return None
            return item.get('value', '')
        except Exception as e:
            logger.error(f'Error getting item for key {clean_key}: {e}')
            return None
    
    @staticmethod
    def set_value(key: str, value: str, expire_sec: int = 60 * 60 * 24):
        if not key or not isinstance(key, str):
            logger.error(f'Invalid key for set_value: {key}')
            return
            
        # 使用新的清理函数来处理特殊字符
        clean_key = DynamoDBKayValue.clean_key_for_dynamodb(key)
        if not clean_key:
            logger.error(f'Key filtered out due to special characters: {key}')
            return
            
        logger.info(f'Setting value for key: {clean_key}')
        table = DynamoDBKayValue.get_db_table()
        
        try:
            table.put_item(Item={
                'key': clean_key, 
                'value': value, 
                'create_time': int(time.time()), 
                'expire_sec': expire_sec
            })
            logger.info(f'Successfully set value for key: {clean_key}')
        except Exception as e:
            logger.error(f'Error setting value for key {clean_key}: {e}')

class Authorization:
    @staticmethod
    def extract_api_key(headers):
        """从请求头中提取API Key"""
        api_key = None
        for key in ['apikey', 'ApiKey', 'Apikey']:
            if key in headers:
                api_key = headers[key]
                break
        logger.info(f"API key extracted: {api_key[:4]}...{api_key[-4:]}" if api_key else "No API key found")
        return api_key

    @staticmethod
    def decode_jwt_payload(token):
        """解码JWT token的有效载荷部分"""
        try:
            # 分离JWT的三个部分
            _, payload_encoded, _ = token.split('.')
            # 添加URL安全的base64编码所需的填充字符
            payload_encoded += '=' * (4 - len(payload_encoded) % 4)
            # 处理URL安全字符
            payload_encoded = payload_encoded.replace('-', '+').replace('_', '/')
            # 解码并解析JSON
            decoded_payload = base64.b64decode(payload_encoded).decode('utf-8')
            return json.loads(decoded_payload)
        except Exception as e:
            logger.error(f"JWT decoding failed: {str(e)}")
            return None

    @staticmethod
    def login_check(event):
        """验证登录状态的函数"""
        logger.info("Login check started")
        dynamodb = get_boto3_resource('dynamodb')
        table = dynamodb.Table('zhuchundashuaige_user')
        headers = event.get('headers', {})

        # 1. 从headers中提取API Key
        api_key = Authorization.extract_api_key(headers)
        if not api_key:
            return False, {
                'statusCode': 401,
                'headers': {'Content-Type': 'application/json'},
                'body': json.dumps({'error': 'Missing API Key in headers'})
            }

        try:
            # 2. Base64解码API Key
            decoded = base64.b64decode(api_key).decode('utf-8')
            logger.info(f"Decoded API key: {decoded}")
            parts = decoded.split('$$')
            if len(parts) != 2:
                logger.warning(f"Invalid API Key format: {decoded}")
                return False, {
                    'statusCode': 400,
                    'headers': {'Content-Type': 'application/json'},
                    'body': json.dumps({'error': 'Invalid API Key format'})
                }

            token, email = parts[0], parts[1]
            logger.info(f"Processing token for email: {email}")

            # 3. 验证JWT格式
            token_parts = token.split('.')
            if len(token_parts) != 3:
                logger.warning(f"Invalid token format: {token}")
                return False, {
                    'statusCode': 403,
                    'headers': {'Content-Type': 'application/json'},
                    'body': json.dumps({'error': 'Invalid token format'})
                }

            # 4. 解码并验证token有效载荷
            claims = Authorization.decode_jwt_payload(token)
            if not claims:
                return False, {
                    'statusCode': 403,
                    'headers': {'Content-Type': 'application/json'},
                    'body': json.dumps({'error': 'Invalid token payload'})
                }

            # 5. 验证token有效期
            current_time = int(time.time())
            if 'exp' not in claims or current_time > claims['exp']:
                logger.warning(f"Token expired for {email}")
                return False, {
                    'statusCode': 403,
                    'headers': {'Content-Type': 'application/json'},
                    'body': json.dumps({'error': 'Token has expired'})
                }

            # 6. 从DynamoDB查询用户
            logger.info(f"Querying user with email: {email}")
            response = table.query(
                IndexName='email-index',
                KeyConditionExpression=Key('email').eq(email)
            )

            items = response.get('Items', [])
            if not items:
                logger.warning(f"No user found for email: {email}")
                return False, {
                    'statusCode': 403,
                    'headers': {'Content-Type': 'application/json'},
                    'body': json.dumps({'error': 'User not found'})
                }

            # 7. 验证用户token
            user_record = items[0]
            if user_record.get('token') != token:
                logger.warning(f"Token mismatch for {email}")
                return False, {
                    'statusCode': 403,
                    'headers': {'Content-Type': 'application/json'},
                    'body': json.dumps({'error': 'Token mismatch'})
                }

            # 8. 返回用户信息
            user_info = {
                'user_id': user_record.get('user_id'),
                'email': email,
                'display_name': user_record.get('display_name'),
                'given_name': user_record.get('given_name'),
                'surname': user_record.get('surname'),
                'user_principal_name': user_record.get('user_principal_name')
            }
            logger.info(f"Login successful for {email}")
            return True, user_info

        except Exception as e:
            logger.error(f"Error during login check: {str(e)}", exc_info=True)
            return False, {
                'statusCode': 500,
                'headers': {'Content-Type': 'application/json'},
                'body': json.dumps({'error': f'Internal server error: {str(e)}'})
            }
            
    @staticmethod
    def get_user_info(event):
        if is_running_on_aws_lambda():
            return Authorization.login_check(event)
        else:
            return True, {
                'user_id': '68e592d2-89f2-4ef3-b75e-a47967e0c0be',
                'email': 'Test@zhuchundashuaige.com',
                'display_name': 'Test User',
                'given_name': 'Test',
                'surname': 'User',
                'user_principal_name': 'Test@zhuchundashuaige.com'
            }